#include "D3D11Client.h"

//====================================================================
//                        Global vars
//====================================================================
PerformanceCounter *PCounter        = NULL;

// D3D11 devices, buffers and states
// The D3D11 device is declared here and set here too
// In Resources.h its declared extern to allow the compiler to
// pick it from here.
IDXGIFactory1 *Factory               = NULL;
IDXGISwapChain *SwapChain            = NULL;
ID3D11Device *gd3dDevice             = NULL;
ID3D11DeviceContext *iCtx            = NULL;

ID3D11InputLayout
    *IL_D3DXVECTOR2                    = NULL,
    *IL_D3DXVECTOR3                    = NULL;

ID3D11SamplerState
    *SS_Point_Wrap                    = NULL,
    *SS_Linear_Wrap                    = NULL,
    *SS_Linear_Clamp                = NULL;

ID3D11Buffer
    *cb_D3DXMATRIX_x1                = NULL,
    *cb_D3DXMATRIX_x2                = NULL,
    *cb_D3DXVECTOR4                    = NULL;

ID3D11RasterizerState
    *RS_CullNone_Solid                = NULL,
    *RS_CullNone_Wire                = NULL,
    *RS_CullFront_Solid                = NULL,
    *RS_CullFront_Wire                = NULL,
    *RS_CullBack_Solid                = NULL,
    *RS_CullBack_Wire                = NULL;

ID3D11BlendState
    *BS_SrcAlpha                    = NULL,
    *BS_InvSrcAlpha                    = NULL,
    *BS_NoBlend                        = NULL;

ID3D11DepthStencilState
    *DSS_NoDepth_NoStencil            = NULL,
    *DSS_TestDepthOnly_NoStencil    = NULL;

ID3D11Texture2D *Tex_2x2            = NULL;

ID3D11RenderTargetView *RTV_Tex_2x2    = NULL;

UINT ShaderCompileFlag                = 0;

char
    vs_ver[32] = { 0 },
    gs_ver[32] = { 0 },
    ps_ver[32] = { 0 };

float fAmbient;

//====================================================================
//                        Performance counter
//====================================================================

PerformanceCounter::PerformanceCounter() {
}

PerformanceCounter::~PerformanceCounter() {
}

void PerformanceCounter::Start() {
    static LARGE_INTEGER out;

    QueryPerformanceCounter( &out );
    start = (__int64)out.QuadPart;

    QueryPerformanceFrequency( &out );
    freq = (double)out.QuadPart;
    freq /= 1e6;
};

void PerformanceCounter::End() {
    static LARGE_INTEGER out;
    double result;

    QueryPerformanceCounter( &out );
    result = (double)(out.QuadPart - start)/freq;
    sprintf( line, "%lf mcrs", result );
    oapiWriteLog( line );
}

void PerformanceCounter::End( char *comment ) {
    static LARGE_INTEGER out;
    double result;

    QueryPerformanceCounter( &out );
    result = (double)(out.QuadPart - start)/freq;
    sprintf( line, "%lf mcrs", result );
    strcat( string, comment );
    strcat( string, line );
    oapiWriteLog( string );
}

char *PerformanceCounter::GetLine() {
    return string;
}

void PerformanceCounter::ShowLine() {
    oapiWriteLog( string );
}

//====================================================================
//                        D3D11Config
//====================================================================

D3D11Config::D3D11Config(D3D11Client *pGC) :
    mGraphicsClient(pGC)
{
    fAmbient = (float)(*(DWORD*)mGraphicsClient->GetConfigParam(CFGPRM_AMBIENTLEVEL)) / 256.0f;

    Width                    = 0;
    Height                    = 0;
    cWidth                    = 0;
    cHeight                    = 0;
    SketchPadMode            = 1;
    TextureMipMapCount        = 0;
    TileLoadingFrequency    = 50;

    FullScreenWindow    = false;
    NormalMaps            = false;
    BumpMaps            = false;
    bPreloadTiles        = false;

    Aspect                = 0.0f;
    ShadowAlpha            = 0.5f;
    MFDTransparency        = 0.9f;

    FILTER_MeshTexture = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    FILTER_PlanetTexture = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    LF_MFDTexture = 1;

    AF_FACTOR_MeshTexture = 1;
    AF_FACTOR_PlanetTexture = 1;

    AAModes = NULL;

    RTFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
    CurrentAAMode.Count = 1;
    CurrentAAMode.Quality = 0;

    CfgFILE = NULL;
}

bool D3D11Config::LoadConfig() {

    FILE *file = NULL;
    if( !(file = fopen( "D3D11Client.cfg", "rb" )) ) {
        SetDefault();
        SaveConfig();
        delete [ ] AAModes;
    }
    else
        fclose( file );

    CfgFILE = oapiOpenFile( "D3D11Client.cfg", FILE_IN, ROOT );

    //GENERAL:
    oapiReadItem_int( CfgFILE, "Supported AA modes", Cfg.AA_count );
    oapiReadItem_int( CfgFILE, "Selected AA mode", Cfg.AA_mode );

    Cfg.AA_count = Cfg.AA_count > 0 ? ( Cfg.AA_count < 10 ? Cfg.AA_count : -1 ) : -1;
    if( Cfg.AA_count != -1 )
        AAModes = new AA_MODE_DESC [Cfg.AA_count];
    else {
        oapiCloseFile( CfgFILE, FILE_IN );
        SetDefault();
        SaveConfig();
        delete [ ] AAModes;
        CfgFILE = oapiOpenFile( "D3D11Client.cfg", FILE_IN, ROOT );
        oapiReadItem_int( CfgFILE, "Supported AA modes", Cfg.AA_count );
        oapiReadItem_int( CfgFILE, "Selected AA mode", Cfg.AA_mode );
        Cfg.AA_count = Cfg.AA_count > 0 ? ( Cfg.AA_count < 10 ? Cfg.AA_count : -1 ) : -1;
        if( Cfg.AA_count == -1 )
            oapiWriteLog( "Unable to load config" );
    }

    int tmp;
    WORD j;
    char line[32], tag[64];
    strcpy( line, "AA_mode" );
    for( j = 0; j < Cfg.AA_count; j++ ) {
        sprintf( tag, "%s_%d", line, j );
        oapiReadItem_string( CfgFILE, tag, AAModes[j].desc );

        sprintf( tag, "%s_%d_count", line, j );
        oapiReadItem_int( CfgFILE, tag, tmp );
        AAModes[j].SDesc.Count = tmp;

        sprintf( tag, "%s_%d_quality", line, j );
        oapiReadItem_int( CfgFILE, tag, tmp );
        AAModes[j].SDesc.Quality = tmp;
    }

    oapiReadItem_int( CfgFILE, "Threading", Cfg.Thread_mode );
    oapiReadItem_int( CfgFILE, "Sketchpad mode", Cfg.Sketchpad_mode );
    oapiReadItem_int( CfgFILE, "MFD filter", Cfg.MFD_filter );
    oapiReadItem_int( CfgFILE, "MFD transparency", Cfg.MFD_transparency );
    oapiReadItem_int( CfgFILE, "PP Effect Enabled", Cfg.PP_effect_enable );

    //VESSELS:
    oapiReadItem_int( CfgFILE, "Mesh mip maps", Cfg.Mesh_Texture_Mip_maps );
    oapiReadItem_int( CfgFILE, "Mesh texture filter", Cfg.Mesh_Texture_Filter );
    oapiReadItem_int( CfgFILE, "Enable mesh normal maps", Cfg.Mesh_Normal_maps );
    oapiReadItem_int( CfgFILE, "Enable mesh bump maps", Cfg.Mesh_Bump_maps );
    oapiReadItem_int( CfgFILE, "Enable mesh specular maps", Cfg.Mesh_Specular_maps );
    oapiReadItem_int( CfgFILE, "Enable mesh emissive maps", Cfg.Mesh_Emissive_maps );

//    oapiReadItem_int( CfgFILE, "Enable PS local lighting", Cfg.PStream_LLights );
//    oapiReadItem_int( CfgFILE, "Enable base local lighting", Cfg.Base_LLights );

    //PLANETS:
    oapiReadItem_int( CfgFILE, "Planet texture filter", Cfg.Planet_Texture_filter );
    oapiReadItem_int( CfgFILE, "Planet tile loading frequency", Cfg.Planet_Tile_loading_Freq );
    oapiCloseFile( CfgFILE, FILE_IN );

    //check values:
    Cfg.Thread_mode = Cfg.Thread_mode > 1 ? 0 : (Cfg.Thread_mode < 0 ? 0 : Cfg.Thread_mode);
    Cfg.Sketchpad_mode = Cfg.Sketchpad_mode > 2 ? 0 : (Cfg.Sketchpad_mode < 0 ? 0 : Cfg.Sketchpad_mode);
    Cfg.Mesh_Texture_Mip_maps = Cfg.Mesh_Texture_Mip_maps > 2 ? 0 : (Cfg.Mesh_Texture_Mip_maps < 0 ? 0 : Cfg.Mesh_Texture_Mip_maps);
    Cfg.Mesh_Texture_Filter = Cfg.Mesh_Texture_Filter > 4 ? 0 : (Cfg.Mesh_Texture_Filter < 0 ? 0 : Cfg.Mesh_Texture_Filter);
    Cfg.Planet_Texture_filter = Cfg.Planet_Texture_filter > 4 ? 0 : (Cfg.Planet_Texture_filter < 0 ? 0 : Cfg.Planet_Texture_filter);
    Cfg.Planet_Tile_loading_Freq = Cfg.Planet_Tile_loading_Freq > 200 ? 50 : (Cfg.Planet_Tile_loading_Freq < 0 ? 50 : Cfg.Planet_Tile_loading_Freq);
    return true;
}

void D3D11Config::SetDefault() {
    Cfg.Thread_mode = 0;
    FindSupportedAAModes();
    Cfg.Sketchpad_mode = 1;
    Cfg.MFD_filter = 1;
    Cfg.MFD_transparency = 90;

    Cfg.Mesh_Texture_Mip_maps = 2;
    Cfg.Mesh_Texture_Filter = 4;
    Cfg.Mesh_Normal_maps = 1;
    Cfg.Mesh_Bump_maps = 0;
    Cfg.Mesh_Specular_maps = 0;
    Cfg.Mesh_Emissive_maps = 0;
    Cfg.PStream_LLights = 0;
    Cfg.Base_LLights = 0;

    Cfg.Planet_Texture_filter = 1;
    Cfg.Planet_Tile_loading_Freq = 50;
}

void D3D11Config::SaveConfig() {

    FILE *file = NULL;
    file = fopen( "D3D11Client.cfg", "wb" );
    fclose( file );

    CfgFILE = NULL;
    CfgFILE = oapiOpenFile( "D3D11Client.cfg", FILE_OUT, ROOT );

/*    if( !CfgFILE ) {
        FILE *file = fopen( "D3D11Client.cfg", "wb" );
        fclose( file );
        CfgFILE = oapiOpenFile( "D3D11Client.cfg", FILE_OUT, ROOT );
        if( !CfgFILE )
            oapiWriteLog( "Unable to create config file" );
    }*/

    //GENERAL:
    oapiWriteItem_int( CfgFILE, "Threading", Cfg.Thread_mode );
    oapiWriteItem_int( CfgFILE, "Supported AA modes", Cfg.AA_count );
    oapiWriteItem_int( CfgFILE, "Selected AA mode", Cfg.AA_mode );

    char line[32], tag[64];
    strcpy( line, "AA_mode" );
    for( WORD j = 0; j < Cfg.AA_count; j++ ) {
        sprintf( tag, "%s_%d", line, j );
        oapiWriteItem_string( CfgFILE, tag, AAModes[j].desc );

        sprintf( tag, "%s_%d_count", line, j );
        oapiWriteItem_int( CfgFILE, tag, AAModes[j].SDesc.Count );

        sprintf( tag, "%s_%d_quality", line, j );
        oapiWriteItem_int( CfgFILE, tag, AAModes[j].SDesc.Quality );
    }

    oapiWriteItem_int( CfgFILE, "Sketchpad mode", Cfg.Sketchpad_mode );
    oapiWriteItem_int( CfgFILE, "MFD filter", Cfg.MFD_filter );
    oapiWriteItem_int( CfgFILE, "MFD transparency", Cfg.MFD_transparency );
    oapiWriteItem_int( CfgFILE, "PP Effect Enabled", Cfg.PP_effect_enable );

    //VESSELS:
    oapiWriteItem_int( CfgFILE, "Mesh mip maps", Cfg.Mesh_Texture_Mip_maps );
    oapiWriteItem_int( CfgFILE, "Mesh texture filter", Cfg.Mesh_Texture_Filter );
    oapiWriteItem_int( CfgFILE, "Enable mesh normal maps", Cfg.Mesh_Normal_maps );
    oapiWriteItem_int( CfgFILE, "Enable mesh bump maps", Cfg.Mesh_Bump_maps );
    oapiWriteItem_int( CfgFILE, "Enable mesh specular maps", Cfg.Mesh_Specular_maps );
    oapiWriteItem_int( CfgFILE, "Enable mesh emissive maps", Cfg.Mesh_Emissive_maps );
//    oapiWriteItem_int( CfgFILE, "Enable PS local lighting", Cfg.PStream_LLights );
//    oapiWriteItem_int( CfgFILE, "Enable base local lighting", Cfg.Base_LLights );

    //PLANETS:
    oapiWriteItem_int( CfgFILE, "Planet texture filter", Cfg.Planet_Texture_filter );
    oapiWriteItem_int( CfgFILE, "Planet tile loading frequency", Cfg.Planet_Tile_loading_Freq );
    oapiCloseFile( CfgFILE, FILE_OUT );
}

void D3D11Config::FindSupportedAAModes() {
    AA_MODE_DESC *tmp = new AA_MODE_DESC [9];
    bool flag[9];
    flag[0] = true;

    strcpy( tmp[0].desc, "No AA" );
    tmp[0].SDesc.Count = 1;
    tmp[0].SDesc.Quality = 0;

    strcpy( tmp[1].desc, "MSAA x2" );
    tmp[1].SDesc.Count = 2;
    tmp[1].SDesc.Quality = 0;

    strcpy( tmp[2].desc, "MSAA x4" );
    tmp[2].SDesc.Count = 4;
    tmp[2].SDesc.Quality = 0;

    strcpy( tmp[3].desc, "MSAA x6" );
    tmp[3].SDesc.Count = 6;
    tmp[3].SDesc.Quality = 0;

    strcpy( tmp[4].desc, "MSAA x8" );
    tmp[4].SDesc.Count = 8;
    tmp[4].SDesc.Quality = 0;

    strcpy( tmp[5].desc, "CSAA x8" );
    tmp[5].SDesc.Count = 4;
    tmp[5].SDesc.Quality = 8;

    strcpy( tmp[6].desc, "CSAA x8Q" );
    tmp[6].SDesc.Count = 8;
    tmp[6].SDesc.Quality = 8;

    strcpy( tmp[7].desc, "CSAA x16" );
    tmp[7].SDesc.Count = 4;
    tmp[7].SDesc.Quality = 16;

    strcpy( tmp[8].desc, "CSAA x16Q" );
    tmp[8].SDesc.Count = 8;
    tmp[8].SDesc.Quality = 16;

    UINT j, ctr = 0, num = 0;
    gd3dDevice->CheckMultisampleQualityLevels( RTFormat, 2, &num );
    flag[1] = num ? true : false;

    gd3dDevice->CheckMultisampleQualityLevels( RTFormat, 4, &num );
    flag[2] = num ? true : false;
    flag[5] = num >= 9 ? true : false;
    flag[7] = num >= 17 ? true : false;

    gd3dDevice->CheckMultisampleQualityLevels( RTFormat, 6, &num );
    flag[3] = num ? true : false;

    gd3dDevice->CheckMultisampleQualityLevels( RTFormat, 8, &num );
    flag[4] = num ? true : false;
    flag[6] = num >= 9 ? true : false;
    flag[8] = num >= 17 ? true : false;

    Cfg.AA_mode = 0;
    Cfg.AA_count = 0;
    for( j = 0; j < 9; j++ )
        if( flag[j] )
            Cfg.AA_count++;

    AAModes = new AA_MODE_DESC [Cfg.AA_count];
    for( j = 0; j < 9; j++ )
        if( flag[j] ) {
            AAModes[ctr] = tmp[j];
            ctr++;
        }

    delete [ ] tmp;
}

void D3D11Config::ApplyConfig() {
    CurrentAAMode = AAModes[Cfg.AA_mode].SDesc;
    SketchPadMode = Cfg.Sketchpad_mode;
    LF_MFDTexture = Cfg.Mesh_Texture_Filter;
    MFDTransparency = (float)Cfg.MFD_transparency/100.0f;

    switch( Cfg.Mesh_Texture_Mip_maps ) {
    case 0:
        TextureMipMapCount = 1;
        break;
    case 1:
        TextureMipMapCount = 4;
        break;
    case 2:
        TextureMipMapCount = 0;
        break;
    }

    FILTER_MeshTexture = SetFilter( Cfg.Mesh_Texture_Filter, AF_FACTOR_MeshTexture );

    NormalMaps = Cfg.Mesh_Normal_maps ? true : false;
    BumpMaps = Cfg.Mesh_Bump_maps ? true : false;
    SpecularMaps = Cfg.Mesh_Specular_maps ? true : false;
    EmissiveMaps = Cfg.Mesh_Emissive_maps ? true : false;

    FILTER_PlanetTexture = SetFilter( Cfg.Planet_Texture_filter, AF_FACTOR_PlanetTexture );

    TileLoadingFrequency = Cfg.Planet_Tile_loading_Freq;
    bPreloadTiles = false;
    enablePPEffects = Cfg.PP_effect_enable;
//    bPreloadTiles = CFG.P_preload_tiles ? true : false;
}

D3D11_FILTER D3D11Config::SetFilter( int value, DWORD &afactor ) {
    if( value < 0 || value > 4 )
        return D3D11_FILTER_MIN_MAG_MIP_LINEAR;

    D3D11_FILTER ret;
    switch( value ) {
    case 0:
        ret = D3D11_FILTER_MIN_MAG_MIP_POINT;
        afactor = 1;
        break;
    case 1:
        ret = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
        afactor = 1;
        break;
    case 2:
        ret = D3D11_FILTER_ANISOTROPIC;
        afactor = 4;
        break;
    case 3:
        ret = D3D11_FILTER_ANISOTROPIC;
        afactor = 8;
        break;
    case 4:
        ret = D3D11_FILTER_ANISOTROPIC;
        afactor = 16;
        break;
    }

    return ret;
}

D3D11Config::~D3D11Config() {
    delete [ ] Modes;
    AdapterOutputDesc.clear();
    Adapters.clear();
    Outputs.clear();
}

void D3D11Config::EnumerateAll( oapi::GraphicsClient::VIDEODATA *vdata ) {
    IDXGIAdapter1 *Adapter;
    IDXGIOutput *Output;
    DXGI_ADAPTER_DESC ADesc;
    DXGI_OUTPUT_DESC ODesc;
    PHYSICAL_MONITOR Monitor;
    UINT AC = 0, OC = 0;
    char *line, cbuf[128];

    HR( CreateDXGIFactory1( __uuidof(IDXGIFactory1), (void**)(&Factory) ) );
    if (!Factory)
    {
        SHOW_MESSAGE("Initialization error", "CreateDXGIFactory1 failed");
    }
    while( Factory->EnumAdapters1( AC, &Adapter ) == S_OK )
    {
        Adapter->GetDesc( &ADesc );
        while( Adapter->EnumOutputs( OC, &Output ) == S_OK )
        {
            Output->GetDesc( &ODesc );

            GetPhysicalMonitorsFromHMONITOR( ODesc.Monitor, 1, &Monitor );
            wcstombs( cbuf, Monitor.szPhysicalMonitorDescription, 128 );
            DestroyPhysicalMonitors( 1, &Monitor );

            line = new char [128];
            AdapterOutputDesc.push_back( line );
            wcstombs( AdapterOutputDesc[OC], ADesc.Description, 128 );
            strcat( AdapterOutputDesc[OC], cbuf );

            Adapters.push_back( Adapter );
            Outputs.push_back( Output );
            OC++;
        }
        AC++;
    }
    NumOutputs = OC;

    if( vdata->deviceidx < 0 || (UINT)vdata->deviceidx > OC )        vdata->deviceidx = 0;
    //if( vdata->modeidx < 0 || (UINT)vdata->modeidx > OC )        vdata->modeidx = 0;

    UINT NumModes;
    Outputs[vdata->deviceidx]->GetDisplayModeList( DXGI_FORMAT_B8G8R8A8_UNORM, 0, &NumModes, NULL );
    Modes = new DXGI_MODE_DESC [NumModes];
    Outputs[vdata->deviceidx]->GetDisplayModeList( DXGI_FORMAT_B8G8R8A8_UNORM, 0, &NumModes, Modes );

    Adapter = Adapters[vdata->deviceidx];
    Output = Outputs[vdata->deviceidx];
    Mode = Modes[vdata->modeidx];

    //FullScreenWindow = vdata->pageflip;
    DriverType = /*vdata->forceenum ? D3D_DRIVER_TYPE_REFERENCE : */D3D_DRIVER_TYPE_UNKNOWN;
    FullScreenWindow = vdata->fullscreen;
    Width = vdata->winw;
    Height = vdata->winh;

    D3D_FEATURE_LEVEL FeatureLevels[ ] = {
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_2,
        D3D_FEATURE_LEVEL_9_1
    };

    UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#ifdef _DEBUG
    flags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

    HRESULT result = D3D11CreateDevice(
        Adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, flags,
		FeatureLevels, 3, D3D11_SDK_VERSION, &gd3dDevice, &(mGraphicsClient->getConfig()->SucceededFL), &iCtx);

    if( !gd3dDevice )
    {
        char errMsg[200];
        sprintf(errMsg, "Device creation failed.\nError code: 0x%08p", result);
        MessageBoxA( gGraphicsClient->mhWindow, errMsg, "Critical error.", MB_ICONERROR | MB_OK );
    }
}

bool tRender = false;


HRESULT CompileFromFile(LPCSTR pSrcFile,CONST D3D10_SHADER_MACRO* pDefines, LPD3D10INCLUDE pInclude,
        LPCSTR pFunctionName, LPCSTR pProfile, UINT Flags1, UINT Flags2, ID3DX11ThreadPump* pPump, ID3D10Blob** ppShader, ID3D10Blob** ppErrorMsgs, HRESULT* pHResult)
{
    Flags1 |= D3D10_SHADER_ENABLE_STRICTNESS;
#ifdef _DEBUG
    Flags1 |= D3D10_SHADER_DEBUG;
#else
    if (pProfile[0] == 'c')
    {
        //for some reason compute shaders compilation fails on level 2 and 3
        Flags1 |= D3D10_SHADER_OPTIMIZATION_LEVEL1;
    }
    else
    {
        Flags1 |= D3D10_SHADER_OPTIMIZATION_LEVEL3;
    }
#endif
    return D3DX11CompileFromFile(pSrcFile, pDefines, pInclude, pFunctionName, pProfile, Flags1, Flags2, pPump, ppShader, ppErrorMsgs, pHResult);
}

std::wstring string2wstring(const std::string& s) 
{ 
    int len; 
    int slength = (int)s.length() + 1; 
    len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0);  
    wchar_t* buf = new wchar_t[len]; 
    MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len); 
    std::wstring r(buf); 
    delete[] buf; 
    return r; 
} 
 
std::string wstring2string(const std::wstring& s) 
{ 
    int len; 
    int slength = (int)s.length() + 1; 
    len = WideCharToMultiByte(CP_ACP, 0, s.c_str(), slength, 0, 0, 0, 0);  
    char* buf = new char[len]; 
    WideCharToMultiByte(CP_ACP, 0, s.c_str(), slength, buf, len, 0, 0);  
    std::string r(buf); 
    delete[] buf; 
    return r; 
} 
